
/*
   Copyright 2007 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <stdio.h>
#include <i386/console.h>
#include <syscall.h>
#include <malloc.h>
#include <Sync.h>
#include <tga.h>
#include "i8042/ps2.h"
#include "display/display.h"

#define DATA_ARRAY_NAME pointer_image
#include "resources/pointer.tga.h"
#undef DATA_ARRAY_NAME

int print(int x, int y, uint fg, uint bg, const char *fmt, ...);

static void displayThread(void *);
static void memoryThread(void *);
static void inputThread(void *);

static display_modeinfo_t modeinfo;
static uint8 *frameBuffer;
static uint8 *backBuffer;

static struct {
	Mutex lock;
	
	int x;
	int y;
	int lastX;
	int lastY;
	
	bool updated;
	
	texinfo image;
} mouseinfo;

int main(void) {
	bios_save();
	window(0, 12, 79, 23);
	place(0, 12);
	
	printf("Test started\n\n");
	
	while (!display_query()) SLEEP(100);
	
	display_getModeInfo(&modeinfo);
	frameBuffer = (uint8 *) display_mapFrameBuffer();
	if (!frameBuffer) {
		EXIT(-1);
	}
	backBuffer = frameBuffer + modeinfo.height*modeinfo.scanWidth;
	
	tga_decode((uint8 *) pointer_image, &mouseinfo.image);
	mouseinfo.x = mouseinfo.lastX = modeinfo.width / 2;
	mouseinfo.y = mouseinfo.lastY = modeinfo.height / 2;
	mouseinfo.updated = true;
	
	// test buffer swap
	if (display_swapBuffers()) {
		display_solidFill(0x996633, 0, 0, modeinfo.width, modeinfo.height);
		display_swapBuffers();
	} else {
		display_swapBuffers();
		display_solidFill(0x000000, 0, 0, modeinfo.width, modeinfo.height);
	}
	
	THREAD(inputThread, 0, "Input Thread");
	THREAD(displayThread, 0, "Display Thread");
	THREAD(memoryThread, 0, "Memory Thread");
}

#include <agg_image_accessors.h>
#include <agg_pixfmt_rgba.h>
#include <agg_rendering_buffer.h>
#include <agg_renderer_base.h>
#include <agg_renderer_scanline.h>
#include <agg_renderer_primitives.h>
#include <agg_renderer_outline_aa.h>
#include <agg_rasterizer_outline.h>
#include <agg_rasterizer_scanline_aa.h>
#include <agg_rasterizer_outline_aa.h>
#include <agg_scanline_u.h>
#include <agg_scanline_p.h>
#include <agg_scanline_bin.h>
#include <agg_span_allocator.h>
#include <agg_span_gouraud_rgba.h>
#include <agg_span_solid.h>

#include <agg_basics.h>
#include <agg_blur.h>
#include <agg_path_storage.h>
#include <agg_conv_stroke.h>
#include <agg_conv_transform.h>
#include <agg_bounding_rect.h>
//#include <agg_pixfmt_rgb.h>
#include <agg_ellipse.h>
#include <agg_rounded_rect.h>

#include <agg_glyph_raster_bin.h>
#include <agg_renderer_raster_text.h>
#include <agg_embedded_raster_fonts.h>

//#define AGG_GRAY8
//#define AGG_BGR24
//#define AGG_RGB24
#define AGG_BGRA32 
//#define AGG_RGBA32 
//#define AGG_ARGB32 
//#define AGG_ABGR32
//#define AGG_RGB565
//#define AGG_RGB555
#include <pixel_formats.h>

#include "simple_blur.h"

typedef agg::renderer_base<pixfmt> base_ren_type;
typedef agg::span_gouraud_rgba<color_type> span_gen_type;
typedef agg::span_allocator<color_type> span_alloc_type;
typedef agg::renderer_scanline_aa_solid<base_ren_type> renderer_solid;
typedef agg::span_simple_blur_rgba32<agg::order_bgra> span_blur_gen;
typedef agg::span_allocator<span_blur_gen::color_type> span_blur_alloc;
typedef agg::glyph_raster_bin<agg::rgba8> glyph_gen;

typedef struct image_buffer {
	agg::rendering_buffer buf;
	pixfmt pixf;
	base_ren_type rb;
	
	image_buffer(uint8 *buffer, int width, int height, int scan) :
		buf(buffer, width, height, scan), pixf(buf), rb(pixf) {
	}
	
	void init(uint8 *buffer, int width, int height, int scan) {
		buf.attach(buffer, width, height, scan);
		rb.reset_clipping(true);
	}
} image_buffer;

#include <Graphics.h>
#include <Color.h>
#include <Frame.h>
#include <Point.h>

class GraphicsRGBA : public gui::Graphics {
	protected:
		uint8 *origBuffer;
		int origWidth;
		int origHeight;
		int origScan;
		
		image_buffer ib;
		
		gui::Color color;
		
		gui::Point t;
	public:
	
	GraphicsRGBA(uint8 *buffer, int width, int height, int scan) :
		origBuffer(buffer), origWidth(width), origHeight(height),
		origScan(scan), ib(buffer, width, height, scan), color(0, 0, 0), t(0, 0)
		{}
		
	virtual ~GraphicsRGBA(void) {}
	
	virtual gui::RefGraphics create(int x, int y, int w, int h) {
		agg::rect_i bounds(0, 0, ib.buf.width()-1, ib.buf.height()-1);
		agg::rect_i rect(x + t.x, y + t.y, x + t.x + w - 1, y + t.y + h - 1);
		
		rect.normalize();
		rect.clip(bounds);
		
		uint8 *buffer = ib.buf.buf() + rect.x1 * 4 + rect.y1 * ib.buf.stride();
		return gui::RefGraphics(new GraphicsRGBA(buffer, rect.x2 - rect.x1 + 1,
			rect.y2 - rect.y1 + 1, ib.buf.stride()));
	}
	
	virtual void fillRect(int x, int y, int w, int h) {
		agg::renderer_primitives<base_ren_type> rp(ib.rb);
		rp.fill_color(color);
		rp.solid_rectangle(x + t.x, y + t.y, x + t.x + w, y + t.y + h);
	}
	
	virtual void setColor(gui::Color color) {
		this->color = color;
	}
	
	virtual void setClip(int x, int y, int w, int h) {
		ib.rb.clip_box(x + t.x, y + t.y, x + t.x + w - 1, y + t.y + h -1);
	}
		
	virtual void setClip(const gui::Rectangle &rect) {
		ib.rb.clip_box(rect.x1 + t.x, rect.y1 + t.y, rect.x2 + t.x,
			rect.y2 + t.y);
	}
	
	virtual gui::Color getColor(void) {
		return color;
	}
	
	virtual void translate(int x, int y) {
		t.x += x;
		t.y += y;
	}
	
	virtual void setOffset(int x, int y) {
		t.x = x;
		t.y = y;
	}
	
	virtual void drawString(const char *str, int x, int y) {
		glyph_gen glyph(0);
		glyph.font(agg::verdana14_bold);
		agg::renderer_raster_htext_solid<base_ren_type, glyph_gen> rt(ib.rb,
			glyph);
		rt.color(color);
		rt.render_text(x + t.x, y + t.y, str, true);
	}
	
	virtual void drawLine(int x1, int y1, int x2, int y2) {
		agg::renderer_primitives<base_ren_type> rp(ib.rb);
		rp.line_color(color);
		
		rp.line(x1 + t.x, y1 + t.y, x2 + t.x, y2 + t.y, true);
	}
	
	virtual void drawImage(const gui::Image &img, int x, int y) {}
	virtual void fillImage(const gui::Image &img, int x, int y, int w, int h) {}
	virtual void copyImage(const gui::Image &img, int x, int y, int w, int h) {}
	
	virtual void drawRect(int x, int y, int w, int h) {
		agg::renderer_primitives<base_ren_type> rp(ib.rb);
		rp.line_color(color);
		rp.rectangle(x + t.x, y + t.y, x + t.x + w, y + t.y + h);
	}
	
	virtual void drawRect(int x, int y, int w, int h, int radius) {
		renderer_solid rs(ib.rb);
		rs.color(color);
		
		agg::rasterizer_scanline_aa<> ras;
		agg::scanline_p8 sl;
		
		agg::rounded_rect shape(x + t.x, y + t.y, x + t.x + w, y + t.y + h,
			radius);
		agg::conv_stroke<agg::rounded_rect> shape_stroke1(shape);
		shape_stroke1.width(1.0);
		
		ras.add_path(shape_stroke1);
		agg::render_scanlines(ras, sl, rs);
	}
	
	virtual void fillRect(int x, int y, int w, int h, int radius) {
		renderer_solid rs(ib.rb);
		rs.color(color);
		
		agg::rasterizer_scanline_aa<> ras;
		agg::scanline_p8 sl;
		
		agg::rounded_rect shape(x + t.x, y + t.y, x + t.x + w, y + t.y + h,
			radius);
		
		ras.add_path(shape);
		agg::render_scanlines(ras, sl, rs);
	}
	
	virtual void blurRect(int x, int y, int w, int h, int radius) {
		agg::rendering_buffer buf(origBuffer + (x + t.x) * 4
			+ (y + t.y) * origScan, w, h, origScan);
		agg::pixfmt_rgba32 pixf(buf);
		agg::stack_blur_rgba32(pixf, radius, radius);
	}
	
	virtual void flush(void) {}
};

class Screen {
	protected:
	
	image_buffer back;
	image_buffer front;
	
	image_buffer cursor;
	image_buffer save;
	int saveX, saveY;
	
	bool cursorAdded;
	
	
	// update region
	agg::rect_i region;
	bool updated;
	
	public:
	
	Screen(uint8 *fbuf, uint8 *bbuf, int width, int height, int scan);
	
	void flush(void);
	void clear(gui::Color color);
	
	void setCursor(uint8 *image, int width, int height, int scan);
	
	gui::RefGraphics getGraphics(void);
	
	void addCursor(int x, int y);
	void removeCursor(void);
	
	gui::Rectangle getRegion(void);
	void invalidateRegion(gui:: Rectangle r);
};

Screen::Screen(uint8 *fbuf, uint8 *bbuf, int width, int height, int scan) :
	back(bbuf, width, height, scan), front(fbuf, width, height, scan),
	cursor(0, 0, 0, 0), save(0, 0, 0, 0) {
	cursorAdded = false;
	updated = false;
	region.init(back.buf.width(), back.buf.height(), 0, 0);
}

void Screen::flush(void) {
	if (updated) {
		front.rb.copy_from(back.pixf, &region);
		updated = false;
		region.init(back.buf.width(), back.buf.height(), 0, 0);
	}
}

void Screen::clear(gui::Color color) {
	back.rb.clear(color);
	updated = true;
	region.init(0, 0, back.buf.width()-1, back.buf.height()-1);
}

void Screen::setCursor(uint8 *image, int width, int height, int scan) {
	cursor.buf.attach(image, width, height, scan);
	cursor.rb.reset_clipping(true);
	
	if (save.buf.buf()) {
		delete [] (uint8 *) save.buf.buf();
	}
	
	save.buf.attach(new uint8[height*scan], width, height, scan);
	save.rb.reset_clipping(true);
}

gui::RefGraphics Screen::getGraphics(void) {
	return gui::RefGraphics(new GraphicsRGBA(back.buf.buf(), back.buf.width(),
		back.buf.height(), back.buf.stride()));
}

void Screen::addCursor(int x, int y) {
	if (!cursorAdded) {
		agg::rect_i src(x, y, x + save.buf.width(), y + save.buf.height());
		save.rb.copy_from(back.pixf, &src, -x, -y);
			
		saveX = x;
		saveY = y;
		
		back.rb.blend_from(cursor.pixf, 0, x, y);
		
		region = unite_rectangles(region, src);
		updated = true;
		
		cursorAdded = true;
	}
}

void Screen::removeCursor(void) {
	if (cursorAdded) {
		agg::rect_i src(saveX, saveY, saveX + save.buf.width(),
			saveY + save.buf.height());
		back.rb.copy_from(save.pixf, 0, saveX, saveY);
		
		region = unite_rectangles(region, src);
		updated = true;
		
		cursorAdded = false;
	}
}

gui::Rectangle Screen::getRegion(void) {
	return region;
}

void Screen::invalidateRegion(gui::Rectangle r) {
	region = unite_rectangles(region, r);
}

static void displayThread(void *) {
	Screen screen(frameBuffer, backBuffer, modeinfo.width - 200,
		modeinfo.height, modeinfo.scanWidth);
	
	color_type clearColor(0x33, 0x66, 0x99);
	screen.clear(clearColor);
		
	gui::RefGraphics g = screen.getGraphics();
	
	gui::Frame frame("Title");
	frame.setBounds(100, 100, 300, 300);
	
	gui::Frame frame2("Window");
	frame2.setBounds(200, 110, 500, 500);
	//frame2.setBounds(200, 100, 500, 500);
	
	gui::Frame frame3("Application");
	frame3.setBounds(150, 150, 400, 300);
	
	g->setColor(gui::Color(0xff, 0x00, 0x00));
	g->drawString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop", 105, 105);
	
	gui::Frame::prepend(frame.getRef());
	gui::Frame::prepend(frame2.getRef());
	gui::Frame::prepend(frame3.getRef());
	
	/*frame.repaint(g);
	frame2.repaint(g);
	frame3.repaint(g);*/
	
	gui::Frame::repaintVisible(g);
	
	screen.setCursor(mouseinfo.image.mem, mouseinfo.image.width,
		mouseinfo.image.height, mouseinfo.image.width*mouseinfo.image.bpp);
	
	screen.addCursor(mouseinfo.x, mouseinfo.y);
	
	while (true) {
		{
		AutoLock<Mutex> _auto(mouseinfo.lock);
		//mouseinfo.lock.lock();
		if (mouseinfo.updated) {
			screen.removeCursor();
			
			// framebuffer updates go here, after the cursor has been removed
			//gui::Frame::repaintVisible(g, &region);
			/*gui::Rectangle region = screen.getRegion();
			g->setClip(region);
			g->setColor(clearColor);
			g->fillRect(region.x1, region.y1, region.x2 - region.x1,
				region.y2 - region.y1);
			gui::Frame::repaintVisible(g);*/
			
			screen.addCursor(mouseinfo.x, mouseinfo.y);
				
			mouseinfo.updated = false;
		}
		//mouseinfo.lock.unlock();
		}
		
		screen.flush();
		
		//if (updated) {
			/*agg::renderer_primitives<base_ren_type> primitives(front.rb);
			primitives.line_color(agg::rgba(0, 1, 0));
			primitives.rectangle(mouseinfo.x, mouseinfo.y,
				mouseinfo.x + mouseinfo.saveunder.width,
				mouseinfo.y + mouseinfo.saveunder.height);
			
			primitives.line_color(agg::rgba(1, 0, 0));
			primitives.rectangle(mouseinfo.lastX, mouseinfo.lastY,
				mouseinfo.lastX + mouseinfo.saveunder.width,
				mouseinfo.lastY + mouseinfo.saveunder.height);*/
			
		//}
		
		SLEEP(1000/100);
	}
}

#if 0
static void displayThread(void *) {
	struct {
		float width;
		float height;
	} box = { 370, 370 };
	
	uint8 *fbSlice = frameBuffer
		+ ((modeinfo.width/2 - (int) box.width/2)*modeinfo.bpp/8)
		+ ((modeinfo.height/2 - (int) box.height/2)*modeinfo.scanWidth);
	
	agg::rendering_buffer sbuf(fbSlice, (int) box.width, (int) box.height, 
		modeinfo.scanWidth);
	pixfmt spixf(sbuf);
	base_ren_type srb(spixf);
	renderer_solid srs(srb);
	
	uint8 *fbWindow = new uint8[sbuf.height() * sbuf.width() * modeinfo.bpp/8];
	agg::rendering_buffer wbuf(fbWindow, sbuf.width(), sbuf.height(),
		sbuf.width()*modeinfo.bpp/8);
	
	pixfmt wpixf(wbuf);
	base_ren_type wrb(wpixf);
	renderer_solid wrs(wrb);
	
	uint8 *fbLocal = new uint8[sbuf.height() * sbuf.width() * modeinfo.bpp/8];
	agg::rendering_buffer rbuf(fbLocal, sbuf.width(), sbuf.height(),
		sbuf.width()*modeinfo.bpp/8);
	
	pixfmt pixf(rbuf);
	base_ren_type rb(pixf);
	renderer_solid rs(rb);
	
	color_type clearColor(0x33, 0x66, 0x99);
	
	agg::rasterizer_scanline_aa<> ras2;
	agg::scanline_p8 sl;
	agg::scanline_u8 sl2;
		
	/*agg::ellipse ell(rbuf.width()/2, rbuf.height()/2, 100.0, 100.0,
		100);
	agg::conv_stroke<agg::ellipse> ell_stroke1(ell);
	ell_stroke1.width(6.0);
	agg::conv_stroke<agg::conv_stroke<agg::ellipse> > ell_stroke2(ell_stroke1);*/
	
	agg::rounded_rect	shape(rbuf.width()/4, rbuf.height()/4, rbuf.width()*3/4,
		rbuf.height()*3/4, 4.0);
	agg::conv_stroke<agg::rounded_rect> shape_stroke1(shape);
	shape_stroke1.width(6.0);
	agg::conv_stroke<agg::conv_stroke<agg::rounded_rect> > shape_stroke2(shape_stroke1);
	shape_stroke2.width(2.0);
	
	rs.color(agg::rgba(0,0.2,0));
	wrs.color(color_type(0x80, 0x80, 0x80));
		
	span_blur_alloc sba;
	span_blur_gen sbg;
	
	glyph_gen glyph(0);
	glyph.font(agg::verdana14_bold);
	agg::renderer_raster_htext_solid<base_ren_type, glyph_gen> rt(wrb, glyph);
	rt.color(agg::rgba(1, 0, 0));
	
	while (true) {
		wrb.clear(clearColor);
		
		ras2.add_path(shape);
		agg::render_scanlines(ras2, sl, wrs);
		
		rt.render_text(box.width/2, box.height/2, "XYZ", true);
		
		/*ras2.add_path(shape_stroke2);
		agg::render_scanlines(ras2, sl, wrs);
		
		sg.source_image(rbuf);
		ras2.add_path(shape);
		
		rbuf.copy_from(wbuf);
		agg::render_scanlines_aa(ras2, sl2, wrb, sa, sg);
		
		rbuf.copy_from(wbuf);
		agg::render_scanlines_aa(ras2, sl2, wrb, sa, sg);
		rbuf.copy_from(wbuf);
		agg::render_scanlines_aa(ras2, sl2, wrb, sa, sg);*/
		
		sbuf.copy_from(wbuf);
		
		mouseinfo.lock.lock();
		//if (mouseinfo.updated) {
			agg::rendering_buffer cursor(mouseinfo.image.mem,
				mouseinfo.image.width, mouseinfo.image.height,
				mouseinfo.image.width*mouseinfo.image.bpp);
			pixfmt img_pixf(cursor);
			base_ren_type img_rb(img_pixf);
			
			agg::rendering_buffer dest(frameBuffer, modeinfo.width,
				modeinfo.height, modeinfo.scanWidth);
			
			pixfmt dest_pixf(dest);
			base_ren_type dest_rb(dest_pixf);
			
			/*agg::rendering_buffer saveunder(mouseinfo.saveunder.mem,
				mouseinfo.saveunder.width, mouseinfo.saveunder.height,
				mouseinfo.saveunder.width*mouseinfo.saveunder.bpp);
			
			pixfmt save_pixf(saveunder);
			base_ren_type*/
			
			agg::rect_i src = img_rb.clip_box();
			agg::rect_i dst = dest_rb.clip_box();
			dest_rb.clip_rect_area(dst, src, mouseinfo.image.width,
				mouseinfo.image.height);
			dest_rb.blend_from(img_pixf, &src, mouseinfo.x, mouseinfo.y);
				
			mouseinfo.updated = false;
		//}
		mouseinfo.lock.unlock();
		
		if (!display_swapBuffers()) {
			print(8, 13, 0xff0000, 0x336699, "Swap Failed");
		}
		
		SLEEP(1000/30);
	}
	
	/*void *brk = sbrk(0);
	int *array = new int[100];
	
	print(8, 30, 0xffffff, ~0, "%08x %08x", (uint) brk, (uint) array);
	
	delete [] array;
	
	uint32 c0 = 0xf00000;
	uint32 c1 = 0x00f000;
	
	while (true) {
		display_drawString("Testing...", 8, 16, c0, ~0);
		display_gradientLine(0, modeinfo.height/2, modeinfo.width-1,
			modeinfo.height/2, c0, c1);
		
		print(8, 44, 0xffffff, 0x996633, "%08x %08x", c0, c1);
		
		c0 = ((c0 >> 1) | (c0 << 23)) & 0xffffff;
		c1 = ((c1 << 2) | (c1 >> 22)) & 0xffffff;
		
		SLEEP(1000/5);
	}*/
}
#endif

#ifndef ABS
#define ABS(a) ((a) >= 0 ? (a) : -(a))
#endif

static void inputThread(void *) {
	if (!i8042_requestOwnership()) {
		return;
	}
	
	gui::RefFrame window(0);
	int delta;
	
	while (true) {
		input_event_t event;
		i8042_getEvent(&event);
		
		if (event.type = INPUT_EVENT_REL) {
			
			mouseinfo.lock.lock();
			switch (event.code) {
				case REL_X:
					mouseinfo.lastX = mouseinfo.x;
					mouseinfo.updated = true;
					
					mouseinfo.x += event.value;
					
					delta = ABS(mouseinfo.x - mouseinfo.lastX);
					if (delta > 40) {
						mouseinfo.x += event.value * 2;
					} else if (delta > 20) {
						mouseinfo.x += event.value;
					}
					
					if (mouseinfo.x < 0) {
						mouseinfo.x = 0;
					} else if (mouseinfo.x >= modeinfo.width) {
						mouseinfo.x = modeinfo.width-1;
					}
					
					break;
				
				case REL_Y:
					mouseinfo.lastY = mouseinfo.y;
					mouseinfo.updated = true;
					
					mouseinfo.y += event.value;
					
					delta = ABS(mouseinfo.y - mouseinfo.lastY);
					if (delta > 40) {
						mouseinfo.y += event.value * 2;
					} else if (delta > 20) {
						mouseinfo.y += event.value;
					}
					
					if (mouseinfo.y < 0) {
						mouseinfo.y = 0;
					} else if (mouseinfo.y >= modeinfo.height) {
						mouseinfo.y = modeinfo.height-1;
					}
					break;
				
				case REL_Z:
				case REL_WHEEL:
					// = event.value / 32.0;
					break;
			}
			mouseinfo.lock.unlock();
			
			if (window) {
				gui::Rectangle rect;
				window->getBounds(rect);
				window->setLocation(rect.x1 + (mouseinfo.x - mouseinfo.lastX),
					rect.y1 + (mouseinfo.y - mouseinfo.lastY));
				window->invalidate();
			}
		} else if (event.type = INPUT_EVENT_KEY) {
			mouseinfo.lock.lock();
			switch (event.code) {
				case BTN_LEFT:
					if (event.value) {
						window = gui::Frame::hitTest(mouseinfo.x, mouseinfo.y);
					} else {
						window = gui::RefFrame(0);
					}
					break;
				
				case BTN_RIGHT:
					break;
				
				case BTN_MIDDLE:
					break;
			}
			mouseinfo.lock.unlock();
		}
	}
	
	i8042_releaseOwnership();
}

// memory syscalls
#define KMALLINFO() ({											\
	struct mallinfo __mi;										\
	syscall(SYSCALL_MEMORY, SYSCALL_MEMORY_MALLINFO, &__mi);	\
	__mi; })

typedef struct {
	uint32 peak;
	uint32 allocated;
	uint32 freed;
} kpage_info_t;

#define KPAGEINFO() ({											\
	kpage_info_t __kpi;											\
	syscall(SYSCALL_MEMORY,  SYSCALL_MEMORY_PAGEINFO, &__kpi);	\
	__kpi; })

static void memoryThread(void *) {
	uint32 xo = modeinfo.width - 11 * 11;
	uint32 yo = modeinfo.height - 12 * 27;
	
	const uint32 bg = 0; //0x336699;
	
	while (true) {
		// kernel malloc info
		struct mallinfo mi = KMALLINFO();
		
		// system memory usage
		kpage_info_t pi = KPAGEINFO();
		
		print(xo, yo + 12*-1, 0x808080, bg, "Kernel");
		print(xo, yo + 12*0, 0xffffff, bg, "Peak");
		print(xo, yo + 12*1, 0xaaaaaa, bg, "%8u K", mi.usmblks / 1024);
		
		print(xo, yo + 12*2, 0xffffff, bg, "Reserved");
		print(xo, yo + 12*3, 0xaaaaaa, bg, "%8u K", mi.arena / 1024);
		
		print(xo, yo + 12*4, 0xffffff, bg, "Used");
		print(xo, yo + 12*5, 0xaaaaaa, bg, "%8u K", mi.uordblks / 1024);
		
		print(xo, yo + 12*6, 0xffffff, bg, "Free");
		print(xo, yo + 12*7, 0xaaaaaa, bg, "%8u K", mi.fordblks / 1024);
		
		print(xo, yo + 12*8, 0x808080, bg, "System");
		print(xo, yo + 12*9, 0xffffff, bg, "Peak");
		print(xo, yo + 12*10, 0xaaaaaa, bg, "%8u K", pi.peak * 4);
		
		print(xo, yo + 12*11, 0xffffff, bg, "Used");
		print(xo, yo + 12*12, 0xaaaaaa, bg, "%8u K", pi.allocated * 4);
		
		print(xo, yo + 12*13, 0xffffff, bg, "Free");
		print(xo, yo + 12*14, 0xaaaaaa, bg, "%8u K", pi.freed * 4);
		
		// local application malloc info
		mi = mallinfo();
		
		print(xo, yo + 12*15, 0x808080, bg, "Shell");
		print(xo, yo + 12*16, 0xffffff, bg, "Peak");
		print(xo, yo + 12*17, 0xaaaaaa, bg, "%8u K", mi.usmblks / 1024);
		
		print(xo, yo + 12*18, 0xffffff, bg, "Reserved");
		print(xo, yo + 12*19, 0xaaaaaa, bg, "%8u K", mi.arena / 1024);
		
		print(xo, yo + 12*20, 0xffffff, bg, "Used");
		print(xo, yo + 12*21, 0xaaaaaa, bg, "%8u K", mi.uordblks / 1024);
		
		print(xo, yo + 12*22, 0xffffff, bg, "Free");
		print(xo, yo + 12*23, 0xaaaaaa, bg, "%8u K", mi.fordblks / 1024);
		
		SLEEP(200);
	}
}

#include <stdarg.h>
#include <i386/stdlib.h>
int print(int x, int y, uint fg, uint bg, const char *fmt, ...) {
	char buf[4*1024];
	va_list parms;
	int result;
	
	va_start(parms,fmt);
	result = vsprintf(buf, (char *) fmt, parms);
	display_drawString(buf, x, y, fg, bg);
	va_end(parms);
	return(result);
}
